﻿using System;
using System.ComponentModel;
using System.Runtime.Serialization;

using SharpGL.Media3D.NETDesignSurface.Converters;

namespace SharpGL.Media3D
{
    [TypeConverter(typeof(Point3DConverter))]
    [DataContract(/*IsReference = true, */Name = "Point3D", Namespace = "SharpGL")]
    public struct Point3D
    {
        #region Private Fields

        [DataMember()]
        private float x;

        [DataMember()]
        private float y;

        [DataMember()]
        private float z;
        
        #endregion Private Fields

        #region Public Constructors

        public Point3D(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Point3D(Point3D point)
        {
            this.x = point.X;
            this.y = point.Y;
            this.z = point.Z;
        }

        #endregion Public Constructors

        #region Public Methods

        public override int GetHashCode()
        {
            return this.x.GetHashCode() ^ this.y.GetHashCode() ^ this.z.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Point3D))
            {
                return false;
            }

            return Equals(this, (Point3D)obj);
        }

        public bool Equals(Point3D point)
        {
            return Equals(this, point);
        }

        public static bool Equals(Point3D point1, Point3D point2)
        {
            return point1.x.Equals(point2.x) && point1.y.Equals(point2.y) && point1.z.Equals(point2.z);
        }

        public override string ToString()
        {
            return "(" + this.X + "; " + this.Y + "; " + this.Z + ")";
        }

        public static Point3D Add(Point3D point, Vector3D vector)
        {
            return new Point3D(point.x + vector.X, point.y + vector.Y, point.z + vector.Z);
        }

        public static Vector3D Subtract(Point3D point1, Point3D point2)
        {
            return new Vector3D(point1.x - point2.x, point1.y - point2.y, point1.z - point2.z);
        }

        public static Point3D Subtract(Point3D point, Vector3D vector)
        {
            return new Point3D(point.x - vector.X, point.y - vector.Y, point.z - vector.Z);
        }

        public static Point3D Multiply(Point3D point, float scalar)
        {
            return new Point3D(point.x * scalar, point.y * scalar, point.z * scalar);
        }

        public static Point3D Multiply(Point3D point, Matrix matrix)
        {
            if ((Object)matrix == null || matrix.Rows != 4 || matrix.Columns != 4)
                throw new ArgumentException("Dimentions of matrix must be 4x4.");
            
            float X = point.X * (float)matrix[0, 0] + point.Y * (float)matrix[1, 0] + point.Z * (float)matrix[2, 0] + (float)matrix[3, 0];
            float Y = point.X * (float)matrix[0, 1] + point.Y * (float)matrix[1, 1] + point.Z * (float)matrix[2, 1] + (float)matrix[3, 1];
            float Z = point.X * (float)matrix[0, 2] + point.Y * (float)matrix[1, 2] + point.Z * (float)matrix[2, 2] + (float)matrix[3, 2];

            return new Point3D(X, Y, Z);
        }

        public static Point3D Multiply(Matrix matrix, Point3D point)
        {
            if ((Object)matrix == null || matrix.Rows != 4 || matrix.Columns != 4)
                throw new ArgumentException("Dimentions of matrix must be 4x4.");

            float X = point.X * (float)matrix[0, 0] + point.Y * (float)matrix[0, 1] + point.Z * (float)matrix[0, 2] + (float)matrix[0, 3];
            float Y = point.X * (float)matrix[1, 0] + point.Y * (float)matrix[1, 1] + point.Z * (float)matrix[1, 2] + (float)matrix[1, 3];
            float Z = point.X * (float)matrix[2, 0] + point.Y * (float)matrix[2, 1] + point.Z * (float)matrix[2, 2] + (float)matrix[2, 3];

            return new Point3D(X, Y, Z);
        }

        public static Point3D Divide(Point3D point, float scalar)
        {
            float div = 1.0f / scalar;
            return new Point3D(point.x * div, point.y * div, point.z * div);
        }

        /// <summary>
        /// Sets the specified position.
        /// </summary>
        /// <param name="X">The X.</param>
        /// <param name="Y">The Y.</param>
        /// <param name="Z">The Z.</param>
        public void Set(float X, float Y, float Z)
        {
            this.X = X;
            this.Y = Y;
            this.Z = Z;
        }

        public void Push(float X, float Y, float Z)
        {
            this.X += X;
            this.Y += Y;
            this.Z += Z;
        }

        public void Push(Vector3D vector)
        {
            this.X += vector.X;
            this.Y += vector.Y;
            this.Z += vector.Z;
        }

        #endregion Public Methods

        #region Public Operators

        public static Point3D operator +(Point3D point, Vector3D vector)
        {
            return Add(point, vector);
        }

        public static Vector3D operator -(Point3D point1, Point3D point2)
        {
            return Subtract(point1, point2);
        }

        public static Point3D operator -(Point3D point, Vector3D vector)
        {
            return Subtract(point, vector);
        }

        public static Point3D operator *(Point3D point, float scalar)
        {
            return Multiply(point, scalar);
        }

        public static Point3D operator *(Point3D point, Matrix matrix)
        {
            return Multiply(point, matrix);
        }

        public static Point3D operator *(Matrix matrix, Point3D point)
        {
            return Multiply(matrix, point);
        }

        public static Point3D operator /(Point3D point, float scalar)
        {
            return Divide(point, scalar);
        }

        public static bool operator ==(Point3D point1, Point3D point2)
        {
            return point1.Equals(point2);
        }

        public static bool operator !=(Point3D point1, Point3D point2)
        {
            return !point1.Equals(point2);
        }

        public static explicit operator Vector3D(Point3D point)
        {
            return new Vector3D(point.x, point.y, point.z);
        }

        public static implicit operator float[](Point3D point)
        {
            return new float[] { point.X, point.Y, point.Z };
        }

        public static explicit operator Point3D(float[] position)
        {
            return new Point3D(position[0], position[1], position[2]);
        }

        #endregion Public Operators

        #region Public Properties

        public float X
        {
            get { return this.x; }
            set { this.x = value; }
        }

        public float Y
        {
            get { return this.y; }
            set { this.y = value; }
        }

        public float Z
        {
            get { return this.z; }
            set { this.z = value; }
        }

        #endregion Public Properties
    }
}
